#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <iostream>
using namespace std;
/*
信号量：
    信号量是一种特殊的变量，用于解决进程间通信的同步问题。
    信号量的值可以看作是一个资源的数量，当一个进程需要使用资源时，它必须先检查信号量的值是否大于0
    如果大于0，就将信号量的值减1，否则就阻塞等待。
    当一个进程使用完资源后，它必须将信号量的值加1，以通知其他进程资源已经可用。

信号量集：
    信号量集是一个信号量的集合，每个信号量都管理一个资源的数量
    每个信号量都对应一个进程，根据资源数量绝决定资源的阻塞与执行

注意：
   1、信号量集是完成多个进程间同步问题的，一般不进行信息的通信
   2、信号量集的使用，本质上是对多个value值进行管控，每个信号量控制一个进程，在进程执行前，申请一个信号量的资源，执行后，释放另一个信号量的资源
   3、如果当前进程申请的信号量值为0，则当前进程在申请处阻塞，直到其他进程将该信号量中的资源增加到大于0，当前进程才会被唤醒


API:
 1、创建key值
       #include <sys/types.h>
       #include <sys/ipc.h>

       key_t ftok(const char *pathname, int proj_id);       //ftok("/", 'k');
       功能：通过给定的文件以及给定的一个随机值，创建出一个4字节整数的key值，用于system V IPC对象的创建
       参数1：一个文件路径，要求是已经存在的文件路径，提供了key值3字节的内容，其中，文件的设备号占1字节，文件的inode号占2字节
       参数2：一个随机整数，取后8位（1字节）跟前面的文件共同组成key值，必须是非0的数字
       返回值：成功返回key值，失败返回-1并置位错误码

2、通过key值创建信号量集
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

       int semget(key_t key, int nsems, int semflg);
       功能：通过给定的key值创建一个信号量集
       参数1：：key值，该值可以是IPC_PRIVATE,也可以是ftok创建出来的，前者只用于亲缘进程间的通信
       参数2：信号量数组中信号量的个数
       参数3：创建标识
            IPC_CREAT:创建并打开一个信号量集，如果信号量集已经存在，则直接打开
            IPC_EXCL:确保本次创建处理的是一个新的信号量集，如果信号量集已经存在，则报错，错误码位EEXIST
            0664：该信号量集的操作权限
            eg: IPC_CREAT|0664  或者  IPC_CREAT|IPC_EXCL|0664
       返回值：成功返回信号量集的id，失败返回-1并置位错误码


3、关于信号量集的操作：P（申请资源）V（释放资源）
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

       int semop(int semid, struct sembuf *sops, size_t nsops);
       功能：完成对信号量数组的操作
       参数1：信号量数据ID号
       参数2：有关信号量操作的结构体变量起始地址，该结构体中包含了操作的信号量编号和申请还是释放的操作
        struct sembuf
        {
           unsigned short sem_num;  //    要操作的信号量的编号
           short          sem_op;   // 要进行的操作，大于0表示释放(添加)资源，小于0表示申请(使用)资源
           short          sem_flg;  //   操作标识位，0标识阻塞方式，IPC_NOWAIT表示非阻塞
        }
        参数3：本次操作的信号量的个数
        返回值：成功返回0，失败返回-1并置位错误码


4、关于信号量集的控制函数
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

       int semctl(int semid, int semnum, int cmd, ...);
       功能：执行有关信号量集的控制函数，具体控制内容取决于cmd
       参数1：信号量集的ID
       参数2：要操作的信号量的编号，编号是从0开始
       参数3：要执行的操作
            IPC_RMID:表示删除信号量集，cmd为该值时，参数2可以忽略，参数4可以不填
            SETVAL：表示对参数2对应的信号量进行设置操作（初始值）
            GETVAL：表示对参数2对应的信号量进行获取值操作
            SETALL：设置信号量集中所有信号量的值
            GETALL:获取信号量集中的所有信号量的值
            IPC_STAT:表示获取当前信号量集的属性
            IPC_SET:表示设置当前信号量集的属性、
        参数4：根据不同的cmd值，填写不同的参数值，所以该处是一个共用体变量
        union semun {
               int              val;    //   设置信号量的值(只有大于0才能被申请使用资源)
               struct semid_ds *buf;    //   关于信号量集属性的操作
               unsigned short  *array;  //    对于信号量集中所有信号量的操作
               struct seminfo  *__buf;  // Buffer for IPC_INFO  (Linux-specific)
            };
        返回值：
            成功时：SETVAL、IPC_RMID返回0，GETVAL返回当前信号量的值，失败返回-1并置位错误码

        例如：
        1)  给0号信号量设置初始值为1
        union semun us;           //定义一个共用体变量
         us.val = 1;              // 对该共用体变量赋值
         semctl(semid, 0, SETVAL, us); // 该函数就完成了对0号信号量设置初始值为1的操作

        2)  删除信号量集
        semctl(semid, 0, IPC_RMID);
*/
/*
二次封装接口以便使用

// 创建信号量集并初始化:semcount表示本次创建的信号量集中信号灯的个数
int create_sem(int semcount);

// 申请资源操作，semno表示要被申请资源的信号量编号
int P(int semid, int semno);

// 释放资源操作，semno表示要被释放资源的信号量编号
int V(int semid, int semno);

// 删除信号量集
int delete_sem(int semid);
*/

// 创建信号量集并指定信号量个数同时分别初始化;semcount信号量的个数
int creat_sem(int semcount)
{
    // 1.创建key值
    key_t key = ftok("./", 'k');
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }

    // 2.通过key值创建信号量集
    int semid = semget(key, semcount, IPC_CREAT | IPC_EXCL | 0664);
    if (semid == -1)
    {
        if (errno == EEXIST)
        { // 通过错误码判断信号量集是否存在，如果存在返回信号量集的id，避免后续触发信号量再次初始化
            semid = semget(key, semcount, IPC_CREAT | 0644);
            return semid;
        }
        perror("semget error");
        return -1;
    }

    // 3.初始化信号量集
    union semun
    {
        int val;               //   设置信号量的值
        struct semid_ds *buf;  //   关于信号量集属性的操作
        unsigned short *array; //    对于信号量集中所有信号量的操作
        struct seminfo *__buf; // Buffer for IPC_INFO  (Linux-specific)
    } us;

    for (int i = 0; i < semcount; i++)
    {
        us.val = -1;
        printf("请设置第%d个信号量的初始值：", i + 1);
        scanf("%d", &us.val);
        getchar(); // 吸收换行符
        semctl(semid, i, SETVAL, us);
    }

    return semid;
}

// 申请资源操作，semno表示要被申请资源的信号量编号
int P(int semid, int semno)
{
    // 定义一个结构体变量
    struct sembuf buf;
    buf.sem_num = semno; // 要操作的信号编号
    buf.sem_op = -1;     //-1表示要申请该信号量的资源
    buf.sem_flg = 0;     // 表示阻塞形式进行申请
    // 调用semop函数完成资源的申请
    if (semop(semid, &buf, 1) == -1)
    {
        perror("P error");
        return -1;
    }
    return 0;
}

// 释放资源操作，semno表示要被释放资源的信号量编号
int V(int semid, int semno)
{
    // 定义一个结构体变量
    struct sembuf buf;
    buf.sem_num = semno; // 要操作的信号编号
    buf.sem_op = 1;      // 1表示要释放该信号量的资源
    buf.sem_flg = 0;     // 表示阻塞形式进行释放
    // 调用semop函数完成资源的释放
    if (semop(semid, &buf, 1) == -1)
    {
        perror("V error");
        return -1;
    }
    return 0;
}

// 删除信号量集
int delete_sem(int semid)
{
    // 调用semctl函数完成对该信号量集的删除
    if (semctl(semid, 0, IPC_RMID) == -1)
    {
        perror("delete error");
        return -1;
    }
    return 0;
}